
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ECMAScript 继承机制实现</title>
    <script>
        /*
        本地类和宿主类不能作为基类，这样可以防止公用访问编译过的浏览器级的代码，因为这些代码可以被用于恶意攻击。
        选定基类后，就可以创建它的子类了。是否使用基类完全由你决定。有时，你可能想创建一个不能直接使用的基类，
        它只是用于给子类提供通用的函数。在这种情况下，基类被看作抽象类。
        样，ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的，
        而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者，你有权决定最适用的继承方式。
        */
        /*
        第一种 继承实现方式： 对象冒充  听起来有点玄乎
        
        */
        function ClassA(sColor) {
            this.color = sColor;
            this.sayColor = function () {
                alert(this.color);
            };
        }

        /*
        构造 ClassB 函数 
        为 ClassA 赋予了方法 newMethod（请记住，函数名只是指向它的指针）。然后调用该方法，传递给它的是 ClassB 构造函数的参数 
        sColor。最后一行代码删除了对 ClassA 的引用，这样以后就不能再调用它。
        所有新属性和新方法都必须在删除了新方法的代码行后定义。否则，可能会覆盖超类的相关属性和方法：
        */
        function ClassB(sColor){
            this.newMethod = ClassA;
            this.newMethod(sColor);  // 调用了 相当于基类的初始化方法
            delete this.newMethod;
        }

        function ClassC(sColor,sName){

            this.newMethod = ClassA;
            this.newMethod(sColor);  // 调用了 相当于基类的初始化方法
            delete this.newMethod;  // 删除了对ClassA的引用

            this.name = sName;
            this.sayName = function (){
                alert("ClassC" + this.name)   //  
            }
        }

        // 简单测试上面的代码  
        function testClassAB(){

            var objA = new ClassA('blue')
            var objB = new ClassC('red','John')
            objA.sayColor()
            objB.sayColor()
            objB.sayName()  
        }

        /*
        简单冒充来实现多继承  多继承只在C++  中支持 Java ，swift OC 都不支持 
        有趣的是，对象冒充可以支持多重继承。也就是说，一个类可以继承多个超类。用 UML 表示的多重继承机制如下图所示
        如果存在两个类 ClassX 和 ClassY，ClassZ 想继承这两个类，可以使用下面的代码：
        */
        function ClassD(sAddress){
            this.address = sAddress;
            this.sayAddress = function(){
                alert(this.address);   // 弹出 这个 address
            }
        }

        // ClassZ  继承自 ClassA  和  ClassD  其实多继承 在目前的主流编程思想中是不主张的
        function ClassZ (sColor,sAddress){

            this.newMethod = ClassA;
            this.newMethod(sColor);  // 调用了 相当于基类的初始化方法
            delete this.newMethod;

            this.newMethod = ClassD;
            this.newMethod(sAddress);
            delete this.newMethod;
            
        }

        /*
        里存在一个弊端，如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法，ClassY 具有高优先级。
        因为它从后面的类继承。除这点小问题之外，用对象冒充实现多重继承机制轻而易举。
        由于这种继承方法的流行，ECMAScript 的第三版为 Function 对象加入了两个方法，即 call() 和 apply()。
        */
        function testClassZ(){
            var objZ = new ClassZ('green','深圳市福田区华富街道')
            objZ.sayColor()
            objZ.sayAddress()    // 两个都调用一番 
        }

        /*
        第二种实现的方式 call() 方法  还是有点复杂
        call() 方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如：
        */
        function sayColor(sPrefix,sSuffix){
            alert(sPrefix + this.color + sSuffix)
        }

        /*
        ，函数 sayColor() 在对象外定义，即使它不属于任何对象，也可以引用关键字 this。对象 obj 的 color 属性等于 blue。
        调用 call() 方法时，第一个参数是 obj，说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。
        第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配，最后生成的消息 
        "The color is blue, a very nice color indeed." 将被显示出来。
        */
        function testSayColor(){
            var obj = new Object();
            obj.color = 'HelloDeLong';
            sayColor.call(obj,"The Color is "," a very nice color indeed")  // 
        }

        // 新建ClassM 继承自 ClassA 
        function ClassM(sColor,sName){

            ClassA.call(this,sColor);  

            this.name = sName;
            this.sayName = function (){
                alert(this.name)
            }
        }

        function testClassM(){
            var  classM = new ClassM('brown','DeLong')
            classM.sayColor()
            classM.sayName()
        }

        /*
        使用 apply() 方法 
        apply() 方法有两个参数，用作 this 的对象和要传递给函数的参数的数组。例如：
        只是现在调用的是 apply() 方法。调用 apply() 方法时，第一个参数仍是 obj，
        说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个参数是由两个字符串构成的数组，
        与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配，最后生成的消息仍是 "The color is blue, a very nice color indeed."，
        将被显示出来。该方法也用于替换前三行的赋值、调用和删除新方法的代码：
        */
        function testApply(){
            var obj = new Object()
            obj.color = "yellow"
            sayColor.apply(obj,new Array("The color is ", "a very nice color indeed."))
        }

        function ClassN(sColor,sName){
            ClassA.apply(this,new Array(sColor))

            this.name = sName;
            this.sayName = function () {
                alert(this.name);
            };
        }

        function testClassN(){

            var n = testClassN('cocoa','run')
            n.sayColor()
            n.sayName()
        }

        /*
        原型链（prototype chaining）
        之前使用过
        */

        function ClassJ(){
            
        }
        /*
         那么我们认为 ClassJ 是继承自 ClassA 的 ，
         因为想要 ClassA 的所有属性和方法，但又不想逐个将它们 ClassB 的 prototype 属性。还有比把 ClassA 的实例赋予 prototype 属性更好的方法吗？
        */
        ClassJ.prototype = new ClassA('red'); 
        ClassJ.prototype.name = ''
        ClassJ.prototype.sayName = function(){
            alert(this.name)    // 笔者认为: 但是其实这种初始化方法 并没有办法传参 只能新建之后再使用
        }

        function testFuncPrototype(){

            var objA = new ClassA();
            var objB = new ClassJ();

            objA.color = "blue";
            objB.color = "red";   // 

            objB.name = "John";
            objA.sayColor();
            objB.sayColor();
            objB.sayName();

            /*
            ！！！ 注意这里还是有歧义的：对 ClassB 的所有实例，instanceof 为 ClassA 和 ClassB 都返回 true。例如：
            都是 true
            */
            alert(objB instanceof ClassA)   // true 
            alert(objB instanceof ClassJ)   // true  

        }

        /*
        混合方式 实现继承： 可以传参数进去
        这种继承方式使用构造函数定义类，并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式，
        这不是最好的选择。不过如果使用原型链，就无法使用带参数的构造函数了。开发者如何选择呢？答案很简单，两者都用。
        在前一章，我们曾经讲解过创建类的最好方式是用构造函数定义属性，用原型定义方法。这种方式同样适用于继承机制用对象冒充继承构造函数的属性
        用原型链继承 prototype 对象的方法。用这两种方式重写前面的例子，代码如下
        */

        function ClassL(sColor,sName){
            ClassA.call(this,sColor);
            this.name = sName;
        }

        // ClassL.prototype = new ClassA(); // 笔者实验： 这句话可有可无 所谓的混合模式可有可无好吧！！！
        ClassL.prototype.sayName = function (){
            alert(this.name)   // m 
        }
        

        function testMix(){
            // 
            var objA = new ClassA("blue");
            var objB = new ClassL("red", "John");
            objA.sayColor();	//输出 "blue"
            objB.sayColor();	//输出 "red"
            objB.sayName();	//输出 "John"
        }

    </script>
</head>
<body>
    <button onclick="testClassAB()">测试 testClassAB</button>
    <button onclick="testClassZ()">测试 testClassZ</button>  
    <button onclick="testSayColor()">测试 call 函数 </button> 
    <button onclick="testClassM()">测试 call 实现继承 </button>
    <button onclick="testFuncPrototype()">测试 prototype 链实现</button>
    <button onclick="testMix()">测试 混合模式</button>
</body>
</html>